﻿using CommunityToolkit.Mvvm.Input;
using FFmpeg.NET;
using Notifications.Wpf;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Windows.Input;
using VideoTools.Tasks;
using VideoTools.Util;
using VideoTools.vo;
using TaskStatus = VideoTools.vo.TaskStatus;

namespace VideoTools.Commands
{
    public class ConvertCodeCommands : NotifyPropertyBase
    {
        private static volatile ConvertCodeCommands _instance;
        private static readonly object SyncRoot = new object();
        static Dictionary<string, ConvertCodeTaskItem> Task_Context =
            new Dictionary<string, ConvertCodeTaskItem>();
        static Dictionary<string, IProgress<double>> Progress_Context =
            new Dictionary<string, IProgress<double>>();
        private static readonly string GPU_COMMAND =
            "ffmpeg -i #{input} -c:v h264_nvenc -gpu 0 -pix_fmt yuv420p -color_range pc -color_trc bt709 -color_primaries bt709 -colorspace bt709 #{output} -y";
        private static readonly string CPU_COMMAND =
            "ffmpeg -i #{input} -c:v libx264 -pix_fmt yuv420p -color_range pc -color_trc bt709 -color_primaries bt709 -colorspace bt709 #{output} -y";

        public static ConvertCodeCommands Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (SyncRoot)
                    {
                        if (_instance == null)
                        {
                            _instance = new ConvertCodeCommands();
                        }
                    }
                }

                return _instance;
            }
        }

        private ConvertCodeCommands()
        {
            foreach (DeviceType dt in Utils.GetAllEnumValues<DeviceType>())
            {
                DeviceTypes.Add(dt);
            }
        }

        private bool _ClearListEnable = true;

        public bool ClearListEnable
        {
            get => _ClearListEnable;
            set => SetProperty(ref _ClearListEnable, value);
        }

        private bool _DeviceTypeEnable = true;

        public bool DeviceTypeEnable
        {
            get => _DeviceTypeEnable;
            set => SetProperty(ref _DeviceTypeEnable, value);
        }
        private bool _DoCommandEnable = true;

        public bool DoCommandEnable
        {
            get => _DoCommandEnable;
            set => SetProperty(ref _DoCommandEnable, value);
        }
        private bool _DeleteSelectedEnable = true;

        public bool DeleteSelectedEnable
        {
            get => _DeleteSelectedEnable;
            set => SetProperty(ref _DeleteSelectedEnable, value);
        }

        private string _CurrentCommand = GPU_COMMAND;

        public string CurrentCommand
        {
            get => _CurrentCommand;
            set => SetProperty(ref _CurrentCommand, value);
        }

        private ObservableCollection<DeviceType> _DeviceTypes =
            new ObservableCollection<DeviceType>();

        public ObservableCollection<DeviceType> DeviceTypes
        {
            get => _DeviceTypes;
            set => SetProperty(ref _DeviceTypes, value);
        }

        private ICommand _DoCommand;
        private ICommand _OpenFileCommand;
        private ICommand _StopAllCommand;
        private ICommand _DeleteSelectedCommand;
        private ICommand _DeleteDoneCommand;
        private ICommand _ClearListCommand;
        private ICommand _ChangeDeviceTypeCommand;
        private ICommand _CopyTaskLogCommand;

        public ICommand CopyTaskLogCommand =>
            _CopyTaskLogCommand
            ?? (
                _CopyTaskLogCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;
                    ConvertCodeTaskItem model = (ConvertCodeTaskItem)o;
                    Utils.CopyToClipboard(model.CopyLog(), "日志");
                })
            );
        public ICommand ChangeDeviceTypeCommand =>
            _ChangeDeviceTypeCommand
            ?? (
                _ChangeDeviceTypeCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                        return;

                    if (o.ToString().Contains("GPU"))
                    {
                        CurrentCommand = GPU_COMMAND;
                    }
                    else
                    {
                        CurrentCommand = CPU_COMMAND;
                    }
                })
            );

        public ICommand DoCommand =>
            _DoCommand
            ?? (
                _DoCommand = new RelayCommand<object>(o =>
                {
                    if (o == null)
                    {
                        return;
                    }
                    if (
                        string.IsNullOrEmpty(CurrentCommand)
                        || ConvertCodeTaskQueueManager
                            .Instance.Tasks.Where(t => t.Status != TaskStatus.Completed)
                            .Count() == 0
                    )
                    {
                        return;
                    }
                    //CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
                    //CancellationToken token = cancellationTokenSource.Token;
                    //for (int i = 0; i < 100; i++)
                    //{
                    //    Task.Delay(100, token); // 模拟工作
                    //}
                    ConvertCodeTaskQueueManager.Instance.StartProcessing();

                    ClearListEnable = false;
                    DeviceTypeEnable = false;
                    DoCommandEnable = false;
                    DeleteSelectedEnable = false;
                })
            );
        public ICommand OpenFileCommand =>
            _OpenFileCommand
            ?? (
                _OpenFileCommand = new RelayCommand<object>(o =>
                {
                    System.Windows.Forms.OpenFileDialog openFileDialog =
                        new System.Windows.Forms.OpenFileDialog();
                    string filter = "视频文件(";
                    foreach (string allow in AppConstants.__allowVideoFiles)
                    {
                        filter += "*." + allow + ",";
                    }
                    filter = filter.Remove(filter.LastIndexOf(","));
                    filter += ")|";
                    foreach (string allow in AppConstants.__allowVideoFiles)
                    {
                        filter += "*." + allow + ";";
                    }
                    filter = filter.Remove(filter.LastIndexOf(";"));
                    openFileDialog.Filter = filter;
                    openFileDialog.Multiselect = true;
                    openFileDialog.RestoreDirectory = true;
                    openFileDialog.FilterIndex = 1;
                    string[] files = null;
                    if (openFileDialog.ShowDialog() == DialogResult.OK)
                    {
                        files = openFileDialog.FileNames;
                    }
                    else
                    {
                        return;
                    }
                    for (int i = 0; i < files.Length; i++)
                    {
                        string f = files[i];
                        int cnt = ConvertCodeTaskQueueManager.Instance.Tasks.Count;

                        ConvertCodeTaskQueueManager.Instance.AddTask(
                            f,
                            TaskAction,
                            TaskActionBefore,
                            TaskActionAfter
                        );
                    }
                })
            );

        private Action<CancellationToken, IProgress<double>, ConvertCodeTaskItem> TaskAction = (
            token,
            progress,
            item
        ) =>
        {
            try
            {
                Process process = new Process();
                token.Register(() =>
                {
                    if (!process.HasExited)
                    {
                        item.Killed = true;
                        process.Kill();
                    }
                });
                process.StartInfo.FileName = "ffmpeg ";
                process.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
                string param = getParam(item);
                //for (int i = 0; i < 100; i++)
                //{
                //    Task.Delay(100, token); // 模拟工作
                //    //double rx = (Convert.ToDouble(cur) / Convert.ToDouble(duration));
                //    //double pro = rx * 100;
                //    item.Progress = i;
                //    //progress.Report(pro);
                //}
                //bool debug = true;
                //if (debug) return;
                item.Info(AppConstants.DoubleClickMessage);
                item.Info("参数 ffmpeg " + param);
                process.StartInfo.Arguments = param;
                process.StartInfo.CreateNoWindow = true; //显示命令行窗口
                process.StartInfo.UseShellExecute = false;
                IDictionary<string, string?> environment = process.StartInfo.Environment;
                environment["taskItem_Path"] = item.Path;
                Task_Context.Add(item.Path, item);
                Progress_Context.Add(item.Path, progress);
                //将输出信息重定向
                process.StartInfo.RedirectStandardInput = true;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.EnableRaisingEvents = true;
                process.Exited += new EventHandler(scale_Exited);
                process.OutputDataReceived += new DataReceivedEventHandler(
                    scale_OutputDataReceived
                );
                process.ErrorDataReceived += new DataReceivedEventHandler(scale_ErrorDataReceived);
                process.Start();
                //开始异步读取输出
                process.BeginOutputReadLine();
                process.BeginErrorReadLine();
                process.WaitForExit();
            }
            catch (Exception e)
            {
                item.Status = TaskStatus.Failed;
            }
            finally { }
        };

        private Action<CancellationToken, ConvertCodeTaskItem> TaskActionBefore = (token, item) =>
        {
            Engine ffmpeg = new Engine(@"ffmpeg.exe");
            InputFile inputFile = new InputFile(item.Path);
            MetaData data = ffmpeg.GetMetaDataAsync(inputFile, token).GetAwaiter().GetResult();
            if (data == null)
            {
                //Info("视频有问题");
                return;
            }
            item.Format = data.VideoData.Format;
            item.ColorModel = data.VideoData.ColorModel;
            item.Duration = TimeSpan.FromSeconds((int)data.Duration.TotalSeconds);
        };

        static void scale_OutputDataReceived(Object sender, DataReceivedEventArgs e) { }

        static void scale_ErrorDataReceived(Object sender, DataReceivedEventArgs e)
        {
            Process process = (Process)sender;
            string path = process.StartInfo.Environment["taskItem_Path"];
            ConvertCodeTaskItem taskItem = Task_Context[path];
            IProgress<double> progress = Progress_Context[path];
            string info = e.Data;
            if (info == null)
            {
                return;
            }
            taskItem.Info(e.Data);
            foreach (string keyword in "frame,fps,size,time,bitrate,speed".Split(','))
            {
                if (!info.Contains(keyword))
                {
                    return;
                }
            }
            Match match = Regex.Match(info, "^frame.+time=(.+)bitrate=.+");
            if (match.Success)
            {
                string time = match.Groups[1].Value;
                if (time.Contains("."))
                {
                    time = time.Split(".")[0];
                }
                string[] times = time.Split(":");
                int cur =
                    int.Parse(times[0]) * 60 * 60 + int.Parse(times[1]) * 60 + int.Parse(times[2]);

                TimeSpan Duration = taskItem.Duration;
                int duration = (int)Duration.TotalSeconds;
                double rx = (Convert.ToDouble(cur) / Convert.ToDouble(duration));
                taskItem.Progress = rx;
                DateTime now = DateTime.Now;
                taskItem.SpendTime = new TimeSpan(now.Ticks - taskItem.startTime.Ticks).ToString(
                    @"hh\:mm\:ss"
                );
                Serilog.Log.Information("进度 " + taskItem.Progress);
            }
        }

        static void scale_Exited(Object sender, EventArgs e)
        {
            Process process = (Process)sender;
            string path = process.StartInfo.Environment["taskItem_Path"];
            ConvertCodeTaskItem taskItem = Task_Context[path];
            taskItem.Info("任务退出" + path);
            Task_Context.Remove(path);
            Progress_Context.Remove(path);
        }

        private static string getParam(ConvertCodeTaskItem item)
        {
            //cpu和gpu命令已在上层处理好了
            string param = ConvertCodeCommands.Instance.CurrentCommand;
            param = param.Replace("ffmpeg ", "");
            param = param.Replace("#{input} ", "\"" + item.Path + "\" ");
            string path = System.IO.Path.GetDirectoryName(item.Path);
            FileInfo f = new FileInfo(item.Path);
            string outFile = null;
            string output = f.Name.Remove(f.Name.LastIndexOf("."));
            string outPutEndWith = "__vfc.mp4";

            output = "\"" + path + "\\" + output + outPutEndWith + "\" ";
            param = param.Replace("#{output} ", output);
            item.OutPath = outFile;
            return param;
        }

        private Action<CancellationToken, ConvertCodeTaskItem> TaskActionAfter = (token, item) =>
        {
            var contentb = new NotificationContent()
            {
                Title = "视频转码成功,点击打开",
                Type = NotificationType.Information,
                Message = item.Path
            };
            AppManager.Instance.NotificationManager.Show(contentb, onClick: () => Process.Start(AppConfig.Instance.VideoPlayerPath, item.OutPath));
        };

        public ICommand StopAllCommand =>
            _StopAllCommand
            ?? (
                _StopAllCommand = new RelayCommand<object>(o =>
                {
                    ConvertCodeTaskQueueManager.Instance.StopAllTasks();
                    ClearListEnable = true;
                    DeviceTypeEnable = true;
                    DoCommandEnable = true;
                    DeleteSelectedEnable = true;
                })
            );

        public ICommand DeleteSelectedCommand =>
            _DeleteSelectedCommand
            ?? (
                _DeleteSelectedCommand = new RelayCommand<object>(o =>
                {
                    ConvertCodeTaskQueueManager.Instance.DeleteSelected();
                })
            );
        public ICommand ClearListCommand =>
            _ClearListCommand
            ?? (
                _ClearListCommand = new RelayCommand<object>(o =>
                {
                    ConvertCodeTaskQueueManager.Instance.ClearTasks();
                })
            );

        public ICommand DeleteDoneCommand =>
            _DeleteDoneCommand
            ?? (
                _DeleteDoneCommand = new RelayCommand<object>(o =>
                {
                    ConvertCodeTaskQueueManager.Instance.ClearCompletedTasks();
                })
            );
    }
}
